package com.chcit.share.generator.service.impl;


import cn.hutool.core.io.FileUtil;
import com.alibaba.fastjson.JSONObject;
import com.chcit.share.dbms.model.Column;
import com.chcit.share.dbms.model.PrimaryKey;
import com.chcit.share.dbms.model.Table;
import com.chcit.share.dbms.service.DatabaseService;
import com.chcit.share.dbms.vo.ConnParam;
import com.chcit.share.dbms.vo.TableInfoSingle;
import com.chcit.share.generator.config.GeneratorProperties;
import com.chcit.share.generator.model.TemplateFileModel;
import com.chcit.share.generator.model.TemplateModel;
import com.chcit.share.generator.service.GenerateService;
import com.chcit.share.generator.service.TemplateFileService;
import com.chcit.share.generator.utils.DataTypeUtils;
import com.chcit.share.generator.vo.ColumnModel;
import com.chcit.share.generator.vo.GenerateModel;
import com.chcit.share.generator.vo.GenerationEntity;
import com.chcit.share.generator.vo.TableModel;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.tomcat.util.http.fileupload.FileUtils;
import org.beetl.core.Configuration;
import org.beetl.core.GroupTemplate;
import org.beetl.core.Template;
import org.beetl.core.resource.ClasspathResourceLoader;
import org.beetl.core.resource.StringTemplateResourceLoader;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.*;
import java.util.function.Consumer;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * 代码生成服务实现
 * @author liuyang
 * @date Nov 9, 2018
 */
@Service
@Slf4j
public class GenerateServiceImpl implements GenerateService {

	public static final String TABLE = "table";
	
	public static final String TEMPLATE_MODEL = "/model.btl";
	public static final String TEMPLATE_MAPPER = "/mapper.btl";
	public static final String TEMPLATE_SQLMAP = "/sqlMap.btl";
	public static final String TEMPLATE_SERVICE = "/service.btl";
	public static final String TEMPLATE_SERVICE_IMPL = "/serviceImpl.btl";
	public static final String TEMPLATE_CONTROLLER = "/controller.btl";
	public static final String TEMPLATE_VIEW = "/view.btl";
	
	public static final String PACKAGE_MODEL = "model";
	public static final String PACKAGE_DAO = "dao";
	public static final String PACKAGE_SQLMAP = "mapper/generator";
	public static final String PACKAGE_SERVICE = "service";
	public static final String PACKAGE_SERVICE_IMPL = "service.impl";
	public static final String PACKAGE_CONTROLLER = "controller";
	public static final String PACKAGE_VIEW = "view";
	
	public static final String SQL_MAP_SUFFIX = "Mapper.xml";
	public static final String MODEL_SUFFIX = ".java";
	public static final String MAPPER_SUFFIX = "Mapper.java";
	public static final String SERVICE_SUFFIX = "Service.java";
	public static final String SERVICE_IMPL_SUFFIX = "ServiceImpl.java";
	public static final String CONTROLLER_SUFFIX = "Controller.java";
	public static final String VIEW_SUFFIX = ".vue";
	@Autowired
	private GeneratorProperties generatorProperties;

	private boolean isOver;
	@Autowired
	private DatabaseService databaseService;

	@Override
	public boolean testConnection(ConnParam connParam) {
		return databaseService.canConnect(connParam);
	}

	@Autowired
	private TemplateFileService templateFileService;

	@Override
	public List<Table> getTables(ConnParam connParam) {

		return databaseService.getTables(connParam);
	}

	@Override
	public GenerateModel getGenerateModel(GenerateModel generateModel) {
		List<TableModel> tableModels = generateModel.getTableModels();
		for(TableModel tableModel:tableModels) {
			ConnParam connParam = generateModel.getConnParam();
			String tableName = tableModel.getName();

			// 设置表对应的实体名
			tableModel.setClassName(StringUtils.capitalize(lineToHump(tableName)));

			// 设置表对应的实例名
			tableModel.setObjectName(StringUtils.uncapitalize(tableModel.getClassName()));

			// 加载表字段
			tableModel.setColumns(getColumns(tableModel, connParam, tableName));

		}
		return generateModel;
	}

	private List<ColumnModel> getColumns(TableModel tableModel, ConnParam connParam, String tableName) {
		List<ColumnModel> columnModels = new ArrayList<>();
		List<Column> columns = databaseService.getColumns(connParam, tableName);
		List<PrimaryKey> primaryKeys = databaseService.getPrimaryKeys(connParam, tableName);
		for(Column column:columns) {
			ColumnModel columnModel = new ColumnModel();
			BeanUtils.copyProperties(column, columnModel);
			// 设置字段对应的对象属性名
			String fieldName = lineToHump(column.getName());
			columnModel.setFieldName(fieldName);
			// 设置属性设置和获取方法
			String setter = "set" + StringUtils.capitalize(fieldName);
			columnModel.setSetter(setter);
			String getter = "get" + StringUtils.capitalize(fieldName);
			columnModel.setGetter(getter);
			// 设置JAVA数据类型
			String javaType = DataTypeUtils.getJavaType(columnModel.getDataType());
			columnModel.setJavaType(javaType);
			String jdbcType = DataTypeUtils.getJdbcType(columnModel.getDataType());
			columnModel.setJdbcType(jdbcType);
			// 设置属性是否为主键
			for(PrimaryKey primaryKey:primaryKeys) {
				if(column.getName().equalsIgnoreCase(primaryKey.getCloumn())) {
					columnModel.setPrimaryKey(true);
					tableModel.setPrimaryKey(columnModel);
					break ;
				}
			}
			 Optional.ofNullable(TableInfoSingle.getInstance().getDictCodes()).ifPresent(dictCodes -> {
				 if(dictCodes.indexOf(column.getName()) > 0){
					 columnModel.setDictColumName(column.getName());
				 }

			 });

			columnModels.add(columnModel);
		}
		return columnModels;
	}

	@Override
	public JSONObject generateModels(GenerateModel generateModel) throws Exception {
		String outPutFolderPath = generateModel.getGenerationEntity().getApiPath();
		String uiPath = generateModel.getGenerationEntity().getUiPath();
		String resourcePath ;
		if(outPutFolderPath == null) {
			outPutFolderPath = System.getProperty("user.dir") + "/src/main";
			resourcePath = System.getProperty("user.dir") + "/src/main/resources";
			generateModel.getGenerationEntity().setApiPath(outPutFolderPath);
			outPutFolderPath = outPutFolderPath + "/java";
		}else{
			 resourcePath = outPutFolderPath+"/resources";
			 outPutFolderPath = outPutFolderPath + "/java";
		}
		if(uiPath == null){
			uiPath = resourcePath;
		}
		ClasspathResourceLoader resourceLoader = new ClasspathResourceLoader(generatorProperties.getTemplatesPath());
		Configuration configuration = Configuration.defaultConfiguration();
		GroupTemplate groupTemplate = new GroupTemplate(resourceLoader, configuration);
		JSONObject tableContent = new JSONObject();
		for(TableModel tableModel:generateModel.getTableModels()) {
			// 设置各类代码包名
			JSONObject fileContent = new JSONObject();
			String basePackage = generateModel.getGenerationEntity().getBasePackage();
			tableModel.setModelPackageName(getPakcageName(basePackage, PACKAGE_MODEL));
			tableModel.setDaoPackageName(getPakcageName(basePackage, PACKAGE_DAO));
			tableModel.setSqlMapPackageName(PACKAGE_SQLMAP);
			tableModel.setServicePackageName(getPakcageName(basePackage, PACKAGE_SERVICE));
			tableModel.setServiceImplPackageName(getPakcageName(basePackage, PACKAGE_SERVICE_IMPL));
			tableModel.setControllerPackageName(getPakcageName(basePackage, PACKAGE_CONTROLLER));
			tableModel.setViewPackageName(PACKAGE_VIEW);
			// generate model
			//fileContent.put(tableModel.getClassName(),generateModel(groupTemplate, tableModel, TEMPLATE_MODEL, generateModel.getOutPutFolderPath(),generateModel.getGenerationEntity().isLocal()));
			generateModel(fileContent,groupTemplate, tableModel, TEMPLATE_MODEL, outPutFolderPath,generateModel.getGenerationEntity().isLocal());
			// generate mapper
			//fileContent.put(tableModel.getDaoPackageName(),generateModel(groupTemplate, tableModel, TEMPLATE_MAPPER, generateModel.getOutPutFolderPath(),generateModel.getGenerationEntity().isLocal()));
			generateModel(fileContent,groupTemplate, tableModel, TEMPLATE_MAPPER, outPutFolderPath,generateModel.getGenerationEntity().isLocal());
			// generate sqlmap.xml
			//fileContent.put(tableModel.getSqlMapPackageName(),generateModel(groupTemplate, tableModel, TEMPLATE_SQLMAP, generateModel.getOutPutFolderPath(),generateModel.getGenerationEntity().isLocal()));
			generateModel(fileContent,groupTemplate, tableModel, TEMPLATE_SQLMAP, resourcePath,generateModel.getGenerationEntity().isLocal());
			// generate service
			//fileContent.put(tableModel.getServicePackageName(),generateModel(groupTemplate, tableModel, TEMPLATE_SERVICE, generateModel.getOutPutFolderPath(),generateModel.getGenerationEntity().isLocal()));
			generateModel(fileContent,groupTemplate, tableModel, TEMPLATE_SERVICE, outPutFolderPath,generateModel.getGenerationEntity().isLocal());
			// generate serviceImpl
			//fileContent.put(tableModel.getServiceImplPackageName(),generateModel(groupTemplate, tableModel, TEMPLATE_SERVICE_IMPL, generateModel.getOutPutFolderPath(),generateModel.getGenerationEntity().isLocal()));
			generateModel(fileContent,groupTemplate, tableModel, TEMPLATE_SERVICE_IMPL, outPutFolderPath,generateModel.getGenerationEntity().isLocal());
			// generate controller
			//fileContent.put(tableModel.getControllerPackageName(),generateModel(groupTemplate, tableModel, TEMPLATE_CONTROLLER, generateModel.getOutPutFolderPath(),generateModel.getGenerationEntity().isLocal()));
			generateModel(fileContent,groupTemplate, tableModel, TEMPLATE_CONTROLLER, outPutFolderPath,generateModel.getGenerationEntity().isLocal());
			// generate view
			//fileContent.put(tableModel.getViewPackageName(),generateModel(groupTemplate, tableModel, TEMPLATE_VIEW, generateModel.getOutPutFolderPath(),generateModel.getGenerationEntity().isLocal()));
			generateModel(fileContent,groupTemplate, tableModel, TEMPLATE_VIEW, uiPath,generateModel.getGenerationEntity().isLocal());
			tableContent.put(tableModel.getName(),fileContent);
		}
		return tableContent;
	}


	@Override
	public String generateModels(Map<String,Object> bind,String templatePath, GenerateModel generateModel) throws Exception {
		StringTemplateResourceLoader resourceLoader = new StringTemplateResourceLoader();
		Configuration configuration = Configuration.defaultConfiguration();
		GroupTemplate groupTemplate = new GroupTemplate(resourceLoader, configuration);
		Template template = groupTemplate.getTemplate(generatorProperties.getTemplatesPath());
		template.binding(bind);
		return template.render();
	}

	@Override
	public List<String> loadTemplateFile(List<TemplateModel> templateList) {
		List<String> list = new ArrayList<String>();
		for (TemplateModel templateEntity : templateList) {
			list.add(getTemplateFile(templateEntity));
		}
		return list;
	}

	private String getTemplateFile(TemplateModel templateEntity) {
		TemplateFileModel entity = new TemplateFileModel();
		entity.setTemplateId(templateEntity.getId());
		return templateFileService.selectOne(entity).getFile();
	}

	private String getPakcageName(String basePackage, String subPackage) {
		// TODO Auto-generated method stub
		return basePackage + "." + subPackage;
	}
	/**
	 * 生成代码
	 * @param groupTemplate
	 * @param tableModel
	 * @param templatePath
	 * @param outPutFolderPath
	 * @throws
	 * @throws Exception
	 */
	private void generateModel(JSONObject json,GroupTemplate groupTemplate, TableModel tableModel, String templatePath, String outPutFolderPath,Boolean isLocal) throws Exception {
		Template template = groupTemplate.getTemplate(templatePath);
		template.binding(TABLE, tableModel);
		List<String> list = getFileName(tableModel,templatePath);
		String fileName = list.get(0) ;
		if(isLocal){
			String filePath = getOutputFile(tableModel, outPutFolderPath, list.get(0),list.get(1));
			if(filePath != null) {
				FileOutputStream os = new FileOutputStream(filePath);
				template.renderTo(os);
				os.close();
			}
		}
		if(json != null){
			json.put(fileName,template.render());
		}
	}


	/**
	 * 生成代码
	 * @param groupTemplate
	 * @param tableModel
	 * @param templatePath
	 * @param outPutFolderPath
	 * @throws
	 * @throws Exception
	 */
	private String generateModel(GroupTemplate groupTemplate, TableModel tableModel, String templatePath, String outPutFolderPath,Boolean isLocal) throws Exception {
		Template template = groupTemplate.getTemplate(templatePath);
		template.binding(TABLE, tableModel);
		if(isLocal){
			List<String> list = getFileName(tableModel,templatePath);
			String filePath = getOutputFile(tableModel, outPutFolderPath, list.get(0),list.get(1));
			if(filePath != null){
				FileOutputStream os = new FileOutputStream(filePath);
				template.renderTo(os);
				os.close();
			}
		}
		return template.render();
	}

	/**
	 * 获取要生成的文件
	 * @param tableModel
	 * @param outPutFolderPath
	 * @param fileName
	 * @return
	 */
	private String getOutputFile(TableModel tableModel, String outPutFolderPath, String fileName,String packageName) {

		outPutFolderPath = outPutFolderPath + "/" + packageName.replaceAll("\\.", "/");
		File outPutFolder = new File(outPutFolderPath);
		if(!outPutFolder.exists()) {
			outPutFolder.mkdirs();
		}
		String filePath = outPutFolderPath + File.separator + fileName;
		File file = new File(filePath);
		if(file.exists()) {
			if(isOver){
				file.delete();
			}else{
				return  null;
			}

		}
		return filePath;
	}
	private List<String> getFileName(TableModel tableModel,String templatePath){
		String packageName = tableModel.getModelPackageName();
		String suffix = MODEL_SUFFIX;
		if(TEMPLATE_MAPPER.equals(templatePath)) {
			packageName = tableModel.getDaoPackageName();
			suffix = MAPPER_SUFFIX;
		} else if(TEMPLATE_SQLMAP.equals(templatePath)) {
			packageName = tableModel.getSqlMapPackageName();
			suffix = SQL_MAP_SUFFIX;
		} else if(TEMPLATE_SERVICE.equals(templatePath)) {
			packageName = tableModel.getServicePackageName();
			suffix = SERVICE_SUFFIX;
		} else if(TEMPLATE_SERVICE_IMPL.equals(templatePath)) {
			packageName = tableModel.getServiceImplPackageName();
			suffix = SERVICE_IMPL_SUFFIX;
		} else if(TEMPLATE_CONTROLLER.equals(templatePath)) {
			packageName = tableModel.getControllerPackageName();
			suffix = CONTROLLER_SUFFIX;
		} else if(TEMPLATE_VIEW.equals(templatePath)) {
			packageName = tableModel.getViewPackageName();
			suffix = VIEW_SUFFIX;
		}
		/*outPutFolderPath = outPutFolderPath + "/" + packageName.replaceAll("\\.", "/");
		File outPutFolder = new File(outPutFolderPath);
		if(!outPutFolder.exists()) {
			outPutFolder.mkdirs();
		}
		String filePath = outPutFolderPath + File.separator + tableModel.getClassName() + suffix;
		File file = new File(filePath);
		if(file.exists()) {
			file.delete();
		}*/
		return   Arrays.asList(tableModel.getClassName() +  suffix,packageName);
	}
	/**
	 * 下划线转驼峰式
	 * @param str
	 * @return
	 */
	public String lineToHump(String str) {
		return DataTypeUtils.lineToHump(str);
	}


	/**
	 * 本地输出代码
	 */
	@Override
	public boolean writeThisFileList(String localPath, String encoded, List<String> fileList, List<TemplateModel> templateList, GenerationEntity ge) {
		for (int i = 0; i < fileList.size(); i++) {
			// 文件路径包括 本地项目路径 + 项目相对路径 + 包路径 + 类的自我路径
			String filePath = localPath + File.separator +
					(StringUtils.isNotEmpty(templateList.get(i).getLocalPath()) ?
							templateList.get(i).getLocalPath().replaceAll("\\.", "\\/") + File.separator : "") +
					getPackagePath(templateList.get(i), ge);
			File path = new File(filePath + File.separator);
			if (!path.exists()) {
				path.mkdirs();
			}

			File file = new File(filePath + File.separator + getFileName(templateList.get(i), ge));
			FileUtil.writeString(fileList.get(i),file, encoded);

		}
		return true;
	}

	/**
	 * 下载代码
	 */
	@Override
	public boolean downThisFileList(HttpServletResponse res, List<String> fileList, List<TemplateModel> templateList, GenerationEntity ge) {
		ZipOutputStream out = null;
		try {
			res.setContentType("application/octet-stream");
			res.setHeader("Content-Disposition", "attachment;filename=GEN_" + ge.getEntityName() + ".zip");
			out = new ZipOutputStream(res.getOutputStream());
			for (int i = 0; i < fileList.size(); i++) {
				out.putNextEntry(new ZipEntry(getPackagePath(templateList.get(i), ge) + File.separator + getFileName(templateList.get(i), ge)));
				out.write(fileList.get(i).getBytes(), 0, fileList.get(i).getBytes().length);
				out.closeEntry();
			}
			return true;
		} catch (Exception e) {
			log.error(e.getMessage(), e);
			return false;
		} finally {
			try {
				out.close();
				res.getOutputStream().flush();
				res.getOutputStream().close();
			} catch (IOException e) {
				log.error(e.getMessage(), e);
			}
		}
	}

	private String getFileName(TemplateModel templateModel, GenerationEntity ge) {
		if (templateModel.getFileName().endsWith("js")) {
			return String.format(templateModel.getFileName(), ge.getEntityName().toLowerCase());
		} else if (templateModel.getFileName().endsWith("html")) {
			return String.format(templateModel.getFileName(), ge.getEntityName().toLowerCase());
		} else if (templateModel.getFileName().endsWith("vue")) {
			return String.format(templateModel.getFileName(), ge.getEntityName().toLowerCase());
		} else if (templateModel.getFileName().endsWith("xml")) {
			return String.format(templateModel.getFileName(), ge.getEntityName());
		} else {
			return String.format(templateModel.getFileName(), ge.getEntityName());
		}
	}


	private String getPackagePath(TemplateModel templateModel, GenerationEntity ge) {
		if (templateModel.getFileName().endsWith("js")) {
			return ge.getJsPackage().replaceAll("\\.", "\\/")
					+ (StringUtils.isNotEmpty(templateModel.getTemplatePath()) ? "/"
					+ templateModel.getTemplatePath().replaceAll("\\.", "\\/") : "");
		} else if (templateModel.getFileName().endsWith("html")) {
			return ge.getHtmlPackage().replaceAll("\\.", "\\/")
					+ (StringUtils.isNotEmpty(templateModel.getTemplatePath()) ? "/"
					+ templateModel.getTemplatePath().replaceAll("\\.", "\\/") : "");
		} else if (templateModel.getFileName().endsWith("vue")) {
			return ge.getHtmlPackage().replaceAll("\\.", "\\/")
					+ (StringUtils.isNotEmpty(templateModel.getTemplatePath()) ? "/"
					+ templateModel.getTemplatePath().replaceAll("\\.", "\\/") : "");
		} else if (templateModel.getFileName().endsWith("xml")) {
			return ge.getXmlPackage().replaceAll("\\.", "\\/")
					+ (StringUtils.isNotEmpty(templateModel.getTemplatePath()) ? "/"
					+ templateModel.getTemplatePath().replaceAll("\\.", "\\/") : "");
		} else {
			return ge.getCodePackage().replaceAll("\\.", "\\/")
					+ (StringUtils.isNotEmpty(templateModel.getTemplatePath()) ? "/"
					+ templateModel.getTemplatePath().replaceAll("\\.", "\\/") : "");
		}
	}
}
